#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <fcntl.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <dirent.h> // 目录操作

char perm[11];

void mode(struct stat *info)
{
    // 得到文件的类型
    switch(info->st_mode & S_IFMT) 
    {
        case S_IFBLK: perm[0]='b'; break; // 块设备
        case S_IFCHR: perm[0]='c'; break; // 字符设备
        case S_IFIFO: perm[0]='p'; break;
        case S_IFSOCK:perm[0]='s'; break;
        case S_IFDIR: perm[0]='d'; break;
        case S_IFREG: perm[0]='-'; break;
        case S_IFLNK: perm[0]='l'; break;
    }

    // 得到文件的权限
    char s[] = {'r', 'w', 'x'};
    for(int i=0; i<9; i++)
        perm[i+1] = (info->st_mode&(0400>>i)) ? s[i%3] : '-';
}

void printInfo(const char *filename, struct stat info)
{
    printf("%s ", perm);   // 输出文件的类型+权限
    printf("%ld ",info.st_nlink); // 输出文件的引用计数（硬链接数）
    printf("%s ", getpwuid(info.st_uid)->pw_name); // 输出文件的所有者(getpwuid())
    printf("%s ", getgrgid(info.st_gid)->gr_name); // 输出文件的所属组(getgrpid())

    if(perm[0] != 'b' && perm[0] != 'c')
        printf("%8ld ", info.st_size); // 输出文件的大小
    else
        printf("%d, %d ", major(info.st_rdev), minor(info.st_rdev)); // 输出文件的设备号(major()/minor())

    printf("%s ", strtok(ctime(&info.st_mtime), "\n"));// 输出文件的最近修改时间（ctime())
    printf("%s\n", filename);     // 输出文件的名字
}

int main(int argc, char const *argv[]) // ./a.out xxx(file/dir)
{
    const char *target = (argc==1) ? "." : argv[1];

    struct stat info;
    stat(target, &info);
    mode(&info);

    // 目录：
    if(perm[0] == 'd')
    {
        // 打开目录获取其操作句柄（handler），列举目录中所有文件的信息（ls -l）
        DIR *dp = opendir(target);
        if(dp == NULL)
        {
            perror("打开目录失败");
            exit(0);
        }

        // 进入目录
        chdir(target);

        // 循环地获取目录下每个文件的属性
        struct dirent *ep;
        while(1)
        {
            ep = readdir(dp);

            if(ep == NULL)
                break;

            stat(ep->d_name, &info);
            mode(&info);
            printInfo(ep->d_name, info);
        }

        closedir(dp);

        return 0;
    }

    // 非目录
    printInfo(target, info);

    return 0;
}
